
/**
 * Baijiahulian.com Inc. Copyright (c) 2014-2016 All Rights Reserved.
 */

package cn.wenhao.javaClassReload.JavaClassModify;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;

import cn.wenhao.javaClassReload.JavaClassModify.classTransformer.MethodInterceptor;
import cn.wenhao.javaClassReload.JavaClassModify.classTransformer.MethodInvokerCreator;
import cn.wenhao.javaClassReload.JavaClassModify.classTransformer.utils.ByteSourceClassLoader;
import cn.wenhao.javaClassReload.JavaClassModify.interfaces.ExecutorAccessor;
import cn.wenhao.javaClassReload.constant.Constant;
import cn.wenhao.javaClassReload.constant.ObjExecutorCache;
import cn.wenhao.javaClassReload.constant.ObjFieldMemberCache;
import cn.wenhao.javaClassReload.javaClassElements.FieldMember;
import cn.wenhao.javaClassReload.javaClassElements.ObjExecutor;
import cn.wenhao.javaClassReload.javaClassElements.ObjFieldMemberExecutor;
import cn.wenhao.javaClassReload.testHelper.ClassPrinter;
import cn.wenhao.javaClassReload.utils.FileUtil;
import cn.wenhao.javaClassReload.utils.GenericsUtils;
import cn.wenhao.javaClassReload.zTestCode.App2;
import lombok.extern.slf4j.Slf4j;

/**
 * @say little Boy, don't be sad.
 * @name Rezar
 * @time Oct 27, 2016
 * @Desc 对某个class进行分析并取出属性及所有的方法信息<br/>
 *       @1 :分析
 */
@Slf4j
public class JavaClassInfoAnalyzer extends ClassNode implements Opcodes {

    private ClassWriter cw;
    private List<MethodNode> reSetMethods = new ArrayList<>();

    private ObjExecutor objExecutor;
    private ObjFieldMemberExecutor objFieldMemberExecutor;

    private boolean isEnd;

    @Override
    public void visitEnd() {
        MethodInterceptor mi = new MethodInterceptor(api, this.cw);
        super.accept(mi);
        transformForNewFieldAndInteraface();
    }

    /**
     * 获取到修改后的字节码
     * 
     * @return
     */
    public byte[] afterTransform() {
        if (!isEnd && GenericsUtils.notNullAndEmpty(this.reSetMethods)) {
            for (MethodNode mn : this.reSetMethods) {
                String[] exces = new String[mn.exceptions.size()];
                for (int i = 0; i < exces.length; i++) {
                    exces[i] = (String) mn.exceptions.get(i);
                }
                MethodVisitor visitMethod = this.cw.visitMethod(mn.access, mn.name, mn.desc, mn.signature, exces);
                mn.accept(visitMethod);
            }
        }
        isEnd = true;
        return this.cw.toByteArray();
    }

    public JavaClassInfoAnalyzer(byte[] buff) {
        objExecutor = new ObjExecutor();
        objFieldMemberExecutor = new ObjFieldMemberExecutor();
        ClassReader cr = new ClassReader(buff);
        this.cw = new ClassWriter(0);
        cr.accept(this, 0);
        this.analyzerClassByte(buff);
        // 进行缓存存储
        ObjExecutorCache.registerObjExecutor(this.name, objExecutor);
        ObjFieldMemberCache.registerObjFieldMember(this.name, objFieldMemberExecutor);
    }

    /**
     * @param buff
     */
    private void analyzerClassByte(byte[] buff) {

        // 获取所有的方法,但没有设置任何属性的访问方法调用器
        // 分析该类的所有成员属性,并进行记录
        transformForFields();

        // 分析所有的方法,并进行不同的处理
        transformForMethods();

    }

    @SuppressWarnings("unchecked")
    private void transformForNewFieldAndInteraface() {

        List<String> interfacesList = this.interfaces;
        String[] interfacesArray = new String[interfacesList.size() + 1];
        for (int i = 0; i < this.interfaces.size(); i++) {
            interfacesArray[i] = interfacesList.get(i);
        }
        interfacesArray[this.interfaces.size()] = Type.getInternalName(ExecutorAccessor.class);
        this.cw.visit(this.version, this.access, this.name, this.signature, this.superName, interfacesArray);

        // add a default field objMethodExecutor , need to be initialize in the static constructor;
        int fieldExecutorAccess = ACC_PUBLIC;
        String fieldExecutorName = Constant.DEFAULT_FIELD_FOR_OBJ_EXECUTOR;
        String fieldExecutorDesc = Type.getDescriptor(this.objExecutor.getClass());

        // add a default field: objFieldMemberExecutor , need to be initialize in the constructor;
        int fieldAccess = ACC_PUBLIC;
        String fieldName = Constant.DEFAULT_FIELD_FOR_OBJ_FIELD_MEMBER;
        String fieldDesc = Type.getDescriptor(this.objFieldMemberExecutor.getClass());

        FieldVisitor fv;
        {
            fv = this.cw.visitField(fieldExecutorAccess, fieldExecutorName, fieldExecutorDesc, null, null);
            fv.visitEnd();
        }
        {
            fv = cw.visitField(fieldAccess, fieldName, fieldDesc, null, null);
            fv.visitEnd();
        }

        MethodVisitor mv;
        {
            mv = this.cw.visitMethod(ACC_PUBLIC, Constant.field_member_executor_name,
                Constant.field_member_executor_desc, null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, this.name, Constant.DEFAULT_FIELD_FOR_OBJ_FIELD_MEMBER,
                Type.getDescriptor(ObjFieldMemberExecutor.class));
            mv.visitInsn(ARETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLocalVariable("this", "L" + this.name + ";", null, l0, l1, 0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }

        {
            mv = cw.visitMethod(ACC_PUBLIC, Constant.field_method_executor_name, Constant.field_method_executor_desc,
                null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, this.name, Constant.DEFAULT_FIELD_FOR_OBJ_EXECUTOR,
                Constant.DEFAULT_FIELD_FOR_OBJ_EXECUTOR_DESC);
            mv.visitInsn(ARETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLocalVariable("this", "L" + this.name + ";", null, l0, l1, 0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }

    }

    /**
     * 
     */
    @SuppressWarnings("unchecked")
    private void transformForMethods() {
        List<MethodNode> methodNodes = this.methods;
        for (MethodNode mn : methodNodes) {
            if (mn.name.equals("<init>") || mn.name.equals("<clinit>")) {
                reSetMethods.add(mn);
                new MethodInvokerCreator(this.name, mn, false);
                continue;
            }
            MethodInvokerCreator methodInvokerCreator = new MethodInvokerCreator(this.name, mn);
            this.objExecutor.addMethodInvoker(this.objExecutor.register(mn.name + mn.desc),
                methodInvokerCreator.createMetodInvoker());
        }
    }

    @SuppressWarnings("unchecked")
    private List<FieldMember> transformForFields() {
        List<FieldNode> fields = this.fields;
        List<FieldMember> fieldMembers = new ArrayList<>(fields.size());
        if (GenericsUtils.notNullAndEmpty(fields)) {
            for (FieldNode fieldNode : fields) {
                FieldMember fieldMember = FieldMember.newInstance(fieldNode);
                fieldMembers.add(fieldMember);
                this.objFieldMemberExecutor.addFieldInvoker(this.objFieldMemberExecutor.register(fieldNode.name),
                    fieldMember);
            }
        }
        return fieldMembers;
    }

    public static void main(String[] args)
        throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException {

        Constant.IS_PRINT = false;
        Integer.class.getMethod("valueOf", int.class);
        byte[] readFromFile = FileUtil.readFromFile(
            ClassLoader.getSystemResource(("cn.wenhao.javaClassReload.zTestCode.App").replace(".", "/") + ".class"));
        log.info("size of resource : {} ", readFromFile.length);
        JavaClassInfoAnalyzer javaClassInfoAnalyzer = new JavaClassInfoAnalyzer(readFromFile);
        byte[] afterTransform = javaClassInfoAnalyzer.afterTransform();
        if (Constant.IS_PRINT) {
            ClassPrinter.print(afterTransform);
        }
        log.info(" javaClassInfoAnalyzer'name is :{} ", javaClassInfoAnalyzer.name);
        String className = javaClassInfoAnalyzer.name.replace("/", ".");
        String classNameOfFile = className.substring(className.lastIndexOf(".") + 1, className.length());
        File file = new File("/Users/bjhl/Desktop/" + classNameOfFile + ".class");
        try {
            FileUtils.writeByteArrayToFile(file, afterTransform);
        } catch (IOException e) {
            e.printStackTrace();
        }

        readFromFile = FileUtil.readFromFile(
            ClassLoader.getSystemResource(("cn.wenhao.javaClassReload.zTestCode.App2").replace(".", "/") + ".class"));
        log.info("size of resource : {} ", readFromFile.length);
        JavaClassInfoAnalyzer javaClassInfoAnalyzerOfApp2 = new JavaClassInfoAnalyzer(readFromFile);
        byte[] afterTransformOfApp2 = javaClassInfoAnalyzerOfApp2.afterTransform();
        if (Constant.IS_PRINT) {
            ClassPrinter.print(afterTransformOfApp2);
        }
        log.info(" javaClassInfoAnalyzer2'name is :{} ", javaClassInfoAnalyzerOfApp2.name);
        String classNameOfApp2 = javaClassInfoAnalyzerOfApp2.name.replace("/", ".");
        String classNameOfFileOfApp2 =
            classNameOfApp2.substring(classNameOfApp2.lastIndexOf(".") + 1, classNameOfApp2.length());
        file = new File("/Users/bjhl/Desktop/" + classNameOfFileOfApp2 + ".class");
        try {
            FileUtils.writeByteArrayToFile(file, afterTransformOfApp2);
        } catch (IOException e) {
            e.printStackTrace();
        }

        Object app2Instance = ByteSourceClassLoader.loadClass(javaClassInfoAnalyzerOfApp2.getClass().getClassLoader(),
            javaClassInfoAnalyzerOfApp2.name.replace("/", "."), afterTransformOfApp2).newInstance();

        Object appInstance = ByteSourceClassLoader.loadClass(javaClassInfoAnalyzer.getClass().getClassLoader(),
            javaClassInfoAnalyzer.name.replace("/", "."), afterTransform).newInstance();

        String methodNameAndDesc = "toString()Ljava/lang/String;";
        log.debug("begin to invoke toString of app ");
        Object invokeMethod2 = ObjExecutorCache.invokeMethod(appInstance, methodNameAndDesc);
        log.info("after invoke with ObjExecutor , the result is : {} ", invokeMethod2);

        ObjFieldMemberCache.putField(Type.getInternalName(App2.class), 23, "age");
        Object field = ObjFieldMemberCache.getField(Type.getInternalName(App2.class), "age");
        log.info("直接获取某个对象的静态属性值为: {} ", field.toString());

        Object invokeMethod = ObjExecutorCache.invokeMethod(app2Instance, "toString()Ljava/lang/String;");
        String string = invokeMethod.toString();
        log.info("app2Instance's toString : {} ", string);

        // 修改方法体执行逻辑
        readFromFile = FileUtil.readFromFile(ClassLoader.getSystemResource(
            "cn.wenhao.javaClassReload.zTestCode.App$checkName$613992014_1".replace(".", "/") + ".class"));
        String checkNameMethodNameAndDesc = "checkName(Ljava/lang/String;ILjava/lang/String;)Z";
        ObjExecutorCache.reloadableMethodInvoker(appInstance.getClass().getName(),
            "cn.wenhao.javaClassReload.zTestCode.App$checkName$613992014_1", checkNameMethodNameAndDesc, readFromFile);

        log.info("begin to invoke toString of app after change methodInvoker ");
        Object invokeToStringAfterReplace = ObjExecutorCache.invokeMethod(appInstance, methodNameAndDesc);
        log.info("invokeToStringAfterReplace : {} ", invokeToStringAfterReplace.toString());

    }

}
